package earlyDetection;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import config.Config;
import earlyDetection.pg.PGEdge;
import network.Device;
import network.Network;
import network.Port;
import networkLoader.FBNetwork;
import org.jgrapht.Graph;
import org.jgrapht.GraphPath;
import org.jgrapht.alg.connectivity.ConnectivityInspector;
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
import org.jgrapht.event.GraphChangeEvent;
import org.jgrapht.event.GraphEdgeChangeEvent;
import org.jgrapht.graph.DefaultDirectedGraph;
import verifier.Ports.Ports;
import verifier.util.BDDEngine;

import java.util.*;

public class PolicyChecker {
    private Graph<Device, PGEdge> pg;
    private HashMap<Integer, Graph<Device, PGEdge>> ecToPg = new HashMap<>();
    private HashMap<Integer, ConnectivityInspector<Device, PGEdge>> ecToCI = new HashMap<>();
    private int hs;
    private Set<Device> closed = new HashSet<>();
    private BDDEngine bddEngine;
    private Network network;
    public boolean stop = false;

    public PolicyChecker(Network network, Graph<Device, PGEdge> pg, BDDEngine bddEngine, int hs) {
        this.network = network;
        this.pg = pg;
        this.bddEngine = bddEngine;
        this.hs = hs;
    }


    public void check(Device newClosed, Collection<Integer> ECs, HashMap<Integer, Ports> ecToPorts) {
        this.closed.add(newClosed);
        if (!this.pg.vertexSet().contains(newClosed)) {
            return;
        }

        for (int ec : ECs) {
            if (bddEngine.and(ec, hs) != 0) {
                if (!ecToPg.containsKey(ec)) {
                    // create pg copy
                    Graph<Device, PGEdge> pg = clonePG();
                    // remove edges for closed switches
                    reducePG(pg, ecToPorts, ec);
                    this.ecToPg.put(ec, pg);
                    ConnectivityInspector<Device, PGEdge> ci = new ConnectivityInspector<>(pg);
                    this.ecToCI.put(ec, ci);
                }
                Graph<Device, PGEdge> pg = ecToPg.get(ec);

                Set<PGEdge> needToRemove = new HashSet<>();
                for (PGEdge edge : pg.outgoingEdgesOf(newClosed)) {
                    if (!ecToPorts.get(ec).getAll().contains(edge.port)) {
                        needToRemove.add(edge);
                    }
                }
                for (PGEdge edge : needToRemove) {
                    pg.removeEdge(edge);
                    GraphEdgeChangeEvent<Device, PGEdge> edgeChangeEvent = new GraphEdgeChangeEvent<>(this, GraphEdgeChangeEvent.EDGE_REMOVED, edge, pg.getEdgeSource(edge), pg.getEdgeTarget(edge));
                    this.ecToCI.get(ec).edgeRemoved(edgeChangeEvent);
                }

//                DijkstraShortestPath dijk = new DijkstraShortestPath(pg);
//                GraphPath<Device, PGEdge> path = dijk.getPath(network.getDevice("rsw-0-0"), network.getDevice("rsw-111-0"));
                Device src = network.getDevice("rsw-0-0");
                Device dst = network.getDevice("rsw-111-0");
                long s = System.nanoTime();
                boolean connected = ecToCI.get(ec).pathExists(src, dst);
                System.out.println("$compute connected: " + (System.nanoTime() - s));
                if (!connected) {
//                if (path == null) {
                    stop = true;
                    System.out.println("cannot reach!");
                    System.out.println("#closed: " + closed.size());
                    System.out.println("#processed updates: " + Config.processedUpdates);
                    System.out.println("$time: " + (System.nanoTime() - Config.startAt));
//                    System.exit(0);
                }
            }
        }
    }

    private Graph<Device, PGEdge> clonePG() {
        Graph<Device, PGEdge> pg = new DefaultDirectedGraph<>(PGEdge.class);
        for (Device device : this.pg.vertexSet()) {
            pg.addVertex(device);
        }
        for (PGEdge edge : this.pg.edgeSet()) {
            Device src = this.pg.getEdgeSource(edge);
            Device dst = this.pg.getEdgeTarget(edge);
            pg.addEdge(src, dst, new PGEdge(src.getPort(src.getName() + ">" + dst.getName())));
        }
        return pg;
    }

    private void reducePG(Graph<Device, PGEdge> pg, HashMap<Integer, Ports> ecToPorts, int ec) {
        for (Device device : pg.vertexSet()) {
            if (!closed.contains(device)) {
                continue;
            }

            Set<PGEdge> needToRemove = new HashSet<>();
            for (PGEdge edge : pg.outgoingEdgesOf(device)) {
                if (!ecToPorts.get(ec).getAll().contains(edge.port)) {
                    needToRemove.add(edge);
                }
            }
            for (PGEdge edge : needToRemove) {
                pg.removeEdge(edge);
            }
        }
    }
}



class PolicyCheckThread implements Runnable {
    private Graph<Device, PGEdge> pg;
    private HashMap<Integer, Graph<Device, PGEdge>> ecToPg = new HashMap<>();
    private HashMap<Integer, ConnectivityInspector<Device, PGEdge>> ecToCI = new HashMap<>();
    private int hs;
    private Set<Device> closed = new HashSet<>();
    private BDDEngine bddEngine;
    private Network network;
    public boolean stop = false;
    @Override
    public void run() {

    }
}